#include "utils.hpp"
#include "offsets.hpp"
#include "main.hpp"
#include <chrono>
#include <algorithm>
#include <cmath>

namespace perf_boost {

extern uint32_t gStartTime;

uint32_t GetTime() {
    return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now().time_since_epoch()).count()) - gStartTime;
}

uint64_t GetWowTimeMs() {
    auto const osGetAsyncTimeMs = reinterpret_cast<GetTimeMsT>(Offsets::OsGetAsyncTimeMs);
    return osGetAsyncTimeMs();
}

uintptr_t *GetLuaStatePtr() {
    typedef uintptr_t *(__fastcall *GETCONTEXT)(void);
    static auto p_GetContext = reinterpret_cast<GETCONTEXT>(Offsets::lua_getcontext);
    return p_GetContext();
}

float fastApproxDistance(C3Vector &vec) {
    // Manhattan distance (fastest, ~2-3x error)
    // return std::abs(vec.x) + std::abs(vec.y) + std::abs(vec.z);

    // Octagonal approximation (fast, ~8% max error)
    float ax = std::abs(vec.x);
    float ay = std::abs(vec.y);
    float az = std::abs(vec.z);

    // Sort components so ax >= ay >= az
    if (ax < ay) std::swap(ax, ay);
    if (ay < az) std::swap(ay, az);
    if (ax < ay) std::swap(ax, ay);

    // Approximation: largest + 0.5*middle + 0.25*smallest
    return ax + 0.5f * ay + 0.25f * az;
}

int ApproximateDistanceBetween(const C3Vector &pos0, const C3Vector &pos1) {
    C3Vector v = {};
    v.x = pos0.x - pos1.x;
    v.y = pos0.y - pos1.y;
    v.z = pos0.z - pos1.z;
    return (int) fastApproxDistance(v);
}

bool IsPlayerInCity() {
    // Get current area ID from memory
    uint32_t areaId = *reinterpret_cast<uint32_t *>(Offsets::ZoneAreaIds);

    // Major city area IDs for WoW 1.12.1
    switch (areaId) {
        case 1537: // Ironforge
        case 1519: // Stormwind City
        case 1657: // Darnassus
        case 1637: // Orgrimmar
        case 1638: // Thunder Bluff
        case 1497: // Undercity
        case 2040: // Alah'Thalas
        case 75:   // Stonard
            return true;
        default:
            return false;
    }
}

int GetRaidMarkForGuid(uint64_t targetGUID) {
    if (targetGUID == 0) {
        return -1;
    }

    for (int result = 0; result < 8; ++result) {
        if (*reinterpret_cast<uint64_t *>(0xb71368 + result * 8) == targetGUID) {
            return result + 1;
        }
    }

    return -1;
}

std::uint64_t ClntObjMgrGetActivePlayerGuid() {
    auto const getActivePlayer = hadesmem::detail::AliasCast<decltype(&ClntObjMgrGetActivePlayerGuid)>(
            Offsets::GetActivePlayer);

    return getActivePlayer();
}

uintptr_t *GetObjectPtr(std::uint64_t guid) {
    uintptr_t *(__stdcall *getObjectPtr)(std::uint64_t) = hadesmem::detail::AliasCast<decltype(getObjectPtr)>(
            Offsets::GetObjectPtr);

    return getObjectPtr(guid);
}

uintptr_t *ClntObjMgrObjectPtr(OBJECT_TYPE_MASK typeMask, std::uint64_t guid) {
    auto const clntObjMgrObjectPtr = reinterpret_cast<ClntObjMgrObjectPtrT>(Offsets::ClntObjMgrObjectPtr);
    return clntObjMgrObjectPtr(typeMask, nullptr, guid, 0);
}

}